#include "console-common.h"
#include "lua-font.h"
#include <cstdlib> // calloc, free, NULL
#include <cstring> // memcpy
#include <string>
#include <windows.h>
#include <iostream> // std::cout, std::endl

#define RANGE_TO_MASK(lower, upper) (~0 << (lower) & ~(~0 << (upper)))

#define RESTRICT_RANGE_D(x, y, lower, upper)\
	if (x < lower) x = lower;\
	if (y > upper) y = upper;
	
#define RESTRICT_RANGE(x, lower, upper) RESTRICT_RANGE_D(x, x, lower, upper)
#define RESTRICT_RANGE_2(x, y, lower, upper)\
	RESTRICT_RANGE(x, lower, upper)\
	RESTRICT_RANGE(y, lower, upper)

/*
String byte codes:
	0x0A ('\n')		newline
	0x0C ('\f')		switch to extra character set
	0x0E			reset color
	0x0F <RGB>		set color (RGB)
	0x11 <color ID>	set color (preset)
	0x12			reverse video on/off
	0x20 to 0x7E	basic ASCII character (from GNU Unifont)
	0x80 to 0xFF	extended ASCII character (editable)
*/

#define EXTASCII_B	0x80
#define EXTASCII_E	(FONT_NUM_CHARS - 1)
#define TOTAL_CUSTOM_CHARS (EXTASCII_E - EXTASCII_B + 1)

#define fchr_size (CHRW * sizeof(fontdata_column_t))
#define chkextascii(c)	((c)>=EXTASCII_B&&(c)<=EXTASCII_E)
#define enum_bound(i, a, b)	for (i = (a); i < (b); i++)
#define start_at_0(i, n)	enum_bound(i, 0, n)
#define enum_extascii(i)	enum_bound(i, EXTASCII_B, EXTASCII_E + 1)

int luacon_allocchr (lua_State * l)
{
	int i, j;
	fontdata_t t = NULL;
	enum_extascii(i)
		if (ascii_font_ptr[i] == NULL) break;

	if (lua_isuserdata(l, 1))
		t = (fontdata_t)lua_touserdata(l, 1);
	else
	{
		j = luaL_optint(l, 1, -1);
		if (j >= 0 && j < EXTASCII_E)
			t = ascii_font_ptr[j];
	}

	if (i <= EXTASCII_E)
	{
		ascii_font_ptr[i] = (fontdata_t)calloc(1, fchr_size);
		if (ascii_font_ptr[i] == NULL)
			i = -1;
	}
	else
		i = -1;
	if (t != NULL && i >= 0)
		memcpy(ascii_font_ptr[i], t, fchr_size);
	lua_pushinteger(l, i);
	return 1;
}

int luacon_alloc_multchr (lua_State * l)
{
	int count = luaL_checkint(l, 1), i, n = 0;
	if (count > TOTAL_CUSTOM_CHARS) count = TOTAL_CUSTOM_CHARS;
	if (count <= 0) return 0;
	luaL_checkstack(l, count, "stack overflow");
	int *freed = new int[TOTAL_CUSTOM_CHARS];

	enum_extascii(i)
		if (ascii_font_ptr[i] == NULL)
		{
			fontdata_t d = (fontdata_t)calloc(1, fchr_size);
			if (d)
				ascii_font_ptr[i] = d,
				freed[n++] = i;
			if (n == count || !d) break;
		}

	if (n >= count)
	{
		start_at_0 (i, count)
			lua_pushinteger(l, freed[i]);
		delete [] freed;
		return count;
	}
	// else n < count
	start_at_0 (i, n)
		free(ascii_font_ptr[freed[i]]),
		ascii_font_ptr[freed[i]] = NULL;
	delete [] freed;
	return 0;
}

int luacon_allocchr_continuous (lua_State * l)
{
	int count = luaL_checkint(l, 1), i, ret;
	if (count <= 0) return 0;
	luaL_checkstack(l, count, "stack overflow");
	bool prev_freed = false, curr_freed;
	int freed_begin, freed_end;

	enum_extascii(i)
	{
		curr_freed = (ascii_font_ptr[i] == NULL);
		if (curr_freed)
		{
			if (!prev_freed)
				freed_begin = i;
			freed_end = i;
			if (freed_end - freed_begin + 1 == count)
				break;
		}
		prev_freed = curr_freed;
	}

	ret = (freed_end - freed_begin + 1 == count ? freed_begin : -1);

	if (ret < 0) goto failed;

	freed_end ++;
	enum_bound(i, freed_begin, freed_end)
		ascii_font_ptr[i] = (fontdata_t)calloc(1, fchr_size);

	if (ascii_font_ptr[i - 1] == NULL)
	{
		enum_bound(i, freed_begin, freed_end)
			free(ascii_font_ptr[i]), ascii_font_ptr[i] = NULL;
		ret = -1;
	}

failed:
	return lua_pushinteger(l, ret), 1;
}

int luacon_freechr (lua_State * l)
{
	bool b = false;
	for (int c, i = lua_gettop(l); i >= 1; i--)
		if (chkextascii(c = luaL_checkint(l, i)))
			if (ascii_font_ptr[c] != NULL)
			{
				b = true,
				free(ascii_font_ptr[c]);
				ascii_font_ptr[c] = NULL;
			}
	lua_pushboolean(l, (int)b);
	return 1;
}

int luacon_checkchr (lua_State * l)
{
	int i = luaL_checkint(l, 1);
	lua_pushboolean(l, (i >= 0x00 && i <= EXTASCII_E && ascii_font_ptr[i] != NULL));
	return 1;
}

int luacon_font_fill (lua_State * l)
{
/*
	fill mode argument:
		0 = clear
		1 = set
		2 = complement
	
	return value:
		true = success
		false = fail
*/

	int args = lua_gettop(l);

	int x1, y1, x2, y2, m, i;
	bool b = false;
	fontdata_t fptr = NULL;

	if (args < 4) goto ret_fail;
	if (lua_isuserdata(l, 1))
		fptr = (fontdata_t)lua_touserdata(l, 1);
	else
	{
		int c = luaL_checkint(l, 1);
		if (chkextascii(c)) fptr = ascii_font_ptr[c];
	}

	fontdata_column_t mask;
	if (fptr != NULL)
	{
		b = true;
		m  = luaL_checkint(l, 2);
		x1 = luaL_checkint(l, 3);
		y1 = luaL_checkint(l, 4);
		x2 = x1+luaL_optint(l, 5, 1);
		y2 = y1+luaL_optint(l, 6, 1);

		RESTRICT_RANGE_D(x1, x2, 0, CHRW);
		RESTRICT_RANGE_2(y1, y2, 0, CHRH);

		mask = RANGE_TO_MASK(CHRH-y2, CHRH-y1);

		enum_bound (i, x1, x2)
			// if (mask & (1 << i))
			switch (m)
			{
				case 0: fptr[i] &= ~mask; break;
				case 1: fptr[i] |=  mask; break;
				case 2: fptr[i] ^=  mask; break;
			}
#ifdef DEBUG
		std::cout << x1 << " " << x2 << std::endl;
		std::cout << y1 << " " << y2 << std::endl;
		std::cout << mask << std::endl;
#endif
	}
ret_fail:
	lua_pushboolean(l, (int)b);
	return 1;
}

int luacon_font_newdata (lua_State * L)
{
	void *d = lua_newuserdata (L, fchr_size);
	memset(d, 0, fchr_size);
	return 1;
}

int luacon_font_getdata (lua_State * L)
{
	int c = luaL_checkint(L, 1);
	void* userdata = NULL;
	if (c >= 0 && c <= EXTASCII_E && ascii_font_ptr[c])
	{
		userdata = lua_newuserdata (L, fchr_size);
		memcpy(userdata, ascii_font_ptr[c], fchr_size);
	}
	else
		lua_pushnil (L);
	return 1;
}

int luacon_font_setdata (lua_State * L)
{
	int c; // len, i, v;
	bool b = false;
	if (lua_gettop(L) < 2) goto ret_fail;
	c = luaL_checkint(L, 1);

	if (chkextascii(c) && ascii_font_ptr[c])
	{
		void* userdata = lua_touserdata(L, 2);
		if (userdata != NULL)
			memcpy(ascii_font_ptr[c], userdata, fchr_size);
		b = true;
	}
ret_fail:
	lua_pushboolean(L, (int)b);
	return 1;
}

int luacon_to_font_data (lua_State * L)
{
	int i, v;
	if (!lua_isuserdata(L, 1))
	{
		if (lua_istable(L, 1))
		{
			fontdata_t userdata = (fontdata_t)lua_newuserdata (L, fchr_size);
			start_at_0 (i, CHRW)
			{
				lua_rawgeti(L, 1, i + 1);
				v = lua_tointeger(L, -1);
				userdata[i] = v;
				lua_pop(L, 1);
			}
		}
		else
			lua_pushnil(L);
	}
	return 1;
}

#define MASK (~(~0 << CHRH))

int luacon_font_is_equal (lua_State * L)
{
	fontdata_t userdata1 = (fontdata_t)lua_touserdata(L, 1);
	fontdata_t userdata2 = (fontdata_t)lua_touserdata(L, 2);
	bool b = false; int i;
	if (userdata1 != NULL && userdata2 != NULL)
	{
		b = true;
		start_at_0 (i, CHRW)
			b &= (((userdata1[i] ^ userdata2[i]) & MASK) == 0);
	}
	lua_pushboolean(L, (int)b);
	return 1;
}

#define FONT_BIT_OP(func, init, opr) \
int func (lua_State * L)\
{\
	fontdata_column_t d[CHRW];\
	int i, j;\
	start_at_0 (i, CHRW) d[i] = (init);\
	for (i = lua_gettop(L); i >= 1; i--)\
	{\
		fontdata_t userdata = (fontdata_t)lua_touserdata(L, i);\
		if (userdata == NULL) return luaL_error(L, "argument #%d must be userdata", i);\
		start_at_0 (j, CHRW) d[j] opr userdata[j];\
	}\
	fontdata_t userdata = (fontdata_t)lua_newuserdata (L, fchr_size);\
	start_at_0 (i, CHRW)\
		userdata[i] = d[i];\
	return 1;\
}

#define return_nil(L) return lua_pushnil(L), 1;

FONT_BIT_OP(luacon_font_bitw_and, MASK, &=)
FONT_BIT_OP(luacon_font_bitw_or, 0, |=)
FONT_BIT_OP(luacon_font_bitw_xor, 0, ^=)

int luacon_font_bitw_not (lua_State * L)
{
	int i;
	fontdata_t userdata1 = (fontdata_t)lua_touserdata(L, 1);
	if (!userdata1) return_nil(L);
	fontdata_t userdata2 = (fontdata_t)lua_newuserdata(L, fchr_size);
	start_at_0 (i, CHRW)
		userdata2[i] = ~userdata1[i];
	return 1;
}

int luacon_font_shift_x (lua_State * L)
{
	int args = lua_gettop(L), i, j, n;
	bool b = false;

	fontdata_t userdata1 = (fontdata_t)lua_touserdata(L, 1);
	if (!userdata1) return_nil(L);
	fontdata_t userdata2 = (fontdata_t)lua_newuserdata(L, fchr_size);
	
	n = luaL_checkint(L, 2);

	if (args >= 3) b = lua_toboolean(L, 3);
	
	start_at_0 (i, CHRW)
	{
		j = i - n;
		if (b)
		{
			// j = (j % CHRW + CHRW) % CHRW;
			j %= CHRW; if (j < 0) j += CHRW;
		}
		if (j >= 0 && j < CHRW)
			userdata2[i] = userdata1[j];
		else
			userdata2[i] = 0;
	}
	return 1;
}

int luacon_font_shift_y (lua_State * L)
{
	int args = lua_gettop(L), i, n;
	bool b = false, dir;
	fontdata_column_t v;

	fontdata_t userdata1 = (fontdata_t)lua_touserdata(L, 1);
	if (!userdata1) return_nil(L);
	fontdata_t userdata2 = (fontdata_t)lua_newuserdata(L, fchr_size);
	
	n = luaL_checkint(L, 2);

	if (args >= 3) b = lua_toboolean(L, 3);
	if (b)
	{
		// n = (n % CHRH + CHRH) % CHRH;
		n %= CHRH; if (n < 0) n += CHRH; 
	}
	else
		dir = (n < 0), dir && (n = -n);

	start_at_0 (i, CHRW)
	{
		v = userdata1[i] & MASK;
		if (b)
			v = (v >> n) | (v << (CHRH - n));
		else
		{
			if (n < CHRH)
				v = (dir ? (v << n) : (v >> n));
			else
				v = 0;
		}
		userdata2[i] = v;
	}
	return 1;
}

static fontdata_column_t __font_data_flip (fontdata_column_t x)
{
	int i;
	fontdata_column_t r;
	for (i = -(-CHRH >> 3); i; i--)
	{
		r = (r << 8) | bit_reverse(x & 0xFF);
		x >>= 8;
	}
	return r >> (-CHRH & 7);
}

int luacon_font_flip (lua_State * L)
{
	int i, j = 0, inc = 1;
	int method = luaL_checkint(L, 2);
	fontdata_t userdata1 = (fontdata_t)lua_touserdata(L, 1);
	if (!userdata1) return_nil(L);
	fontdata_t userdata2 = (fontdata_t)lua_newuserdata(L, fchr_size);
	
	if (method & FONT_FLIP_X)
		j = CHRW - 1, inc = -1;
	
	if (method & FONT_FLIP_Y)
	    start_at_0 (i, CHRW)
			userdata2[j] = __font_data_flip(userdata1[i]), j += inc;
	else
		start_at_0 (i, CHRW)
			userdata2[j] = userdata1[i], j += inc;
	
	return 1;
}

#define _enum_size (TOTAL_CUSTOM_CHARS >> 3)
#define _begin_enum start_at_0 (o, _enum_size) { start_at_0 (p, 8) { i = EXTASCII_B + o * 8 + p;
#define _close_enum }}
int luacon_font_fileop (lua_State * l)
{
	std::string filename = "Fonts\\";
	filename += luaL_checkstring(l, 1);
	bool w = lua_toboolean(l, 2);

	int sz = _enum_size, i, o, p;
	uint8_t *s = (uint8_t*)calloc(1, sz + fchr_size * (EXTASCII_E - EXTASCII_B + 1));
	if (s == NULL) return 0;
	FILE * fh = fopen(filename.c_str(), (w ? "wb" : "rb"));

	if (w)
	{
		CreateDirectory("Fonts", NULL);
		_begin_enum
		if (ascii_font_ptr[i] != NULL)
			s[o] |= 1 << p,
			memcpy(s + sz, ascii_font_ptr[i], fchr_size),
			sz += fchr_size;
		_close_enum
		fwrite(s, sz, 1, fh);
	}
	else
	{
		fread(s, _enum_size, 1, fh);
		enum_extascii(i)
			if (ascii_font_ptr[i] != NULL)
				free(ascii_font_ptr[i]),
				ascii_font_ptr[i] = NULL; // to prevent dangling pointer
		_begin_enum
		if ((s[o] >> p) & 1 && fread(s + _enum_size, fchr_size, 1, fh) == 1)
			ascii_font_ptr[i] = (fontdata_t)malloc(fchr_size),
			memcpy(ascii_font_ptr[i], s + _enum_size, fchr_size);
		_close_enum
	}

	fclose(fh);
	free(s);
	
	return 0;
}
#undef _begin_enum
#undef _close_enum
